﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ServerFrame
{
    /// <summary>
    /// 服务器代理类
    /// </summary>
    public class ServerAgent
    {
        /// <summary>
        /// 套接字
        /// </summary>
        Socket server;

        /// <summary>
        /// 服务器设置
        /// </summary>
        ServerSetting setting;

        /// <summary>
        /// 客户端信号量（服务器所能连接的客户端最大的数量）
        /// </summary>
        Semaphore clientSemaphore;

        /// <summary>
        /// 客户端连接缓存
        /// </summary>
        public UserTokenCache cache;

        public ServerAgent()
        {
            try
            {
                server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
            
        }
        /// <summary>
        /// 启动服务器接口
        /// </summary>
        /// <param name="setting"></param>
        public void Start(ServerSetting setting)
        {
            this.setting = setting;
            clientSemaphore = new Semaphore(setting.maxClient, setting.maxClient);
            try
            {
                // 服务器 绑定 指定IP地址和端口号
                server.Bind(new IPEndPoint(IPAddress.Any, setting.port));
                server.Listen(10);//十台机器
                StartAccept(null);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
            
        }
        /// <summary>
        /// 接收请求
        /// </summary>
        /// <param name="saea">异步套接字操作</param>
        private void StartAccept(SocketAsyncEventArgs saea)
        {
            Console.WriteLine("监听客户都的接入");
            if (saea == null)
            {
                //第一次调用
                saea = new SocketAsyncEventArgs();
                //客户端连接自动执行
                saea.Completed += AcceptCompleted;
            }
            else
            {
                //上一个soceke置空
                saea.AcceptSocket = null;
            }

            //异步接收客户端--返回 异步事件是否挂起
            //true 表示挂起， 没有立刻收到客户端的连接， 
            //          等客户端连接后，通过事件通知服务器有客户端接入，处理客户端连接
            //false 表示没有挂起，有客户端连接进来，处理客户端连接；启动该客户端的消息收发
            //          处理客户端连接：保存客户端连接、通知应用层有客户端接入、启动客户端的消息收发

            bool ret = server.AcceptAsync(saea);
            if (!ret)
            {//有客户端接入

                ProcessAccept(saea);
            }
        }
        /// <summary>
        /// 处理客户端连接服务器
        /// </summary>
        private void ProcessAccept(SocketAsyncEventArgs saea)
        {
            //Console.WriteLine("有客户端接入服务器,需要处理客户端连接服务器");
            //UserToken token = new UserToken();
            UserToken token = cache.Pop();

            token.CloseProcess = ClientClose;
            //保存客户端连接
            token.socket = saea.AcceptSocket;
            //客户端最大数量处理
            clientSemaphore.WaitOne();//信号量减1
            //通知应用层有客户端接入___添加了消息中心
            setting.center.ClientAccept(token);
            //启动客户端的消息收发
            token.StartReceive();
            //继续启动服务器的下一次监听客户端连接
            StartAccept(saea);

        }
        /// <summary>
        /// 异步事件完成 的回调
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AcceptCompleted(object sender, SocketAsyncEventArgs saea)
        {
            ProcessAccept(saea);
        }

        /// <summary>
        /// 客户端断开时调用，处理计数器（信号量），处理缓存。。
        /// </summary>
        /// <param name="token">掉线客户端</param>
        /// <param name="error">掉线原因</param>
        private void ClientClose(UserToken token, string error)
        {
            if (token !=null)
            {
                //TODO:清理用户对象信息
                lock (token)
                {//线程锁，防止当前token掉队
                    token.Close();
                }
                //TODO:通知应用层客户端断开了
                setting.center.ClientClose(token, error);
                //TODO:放回缓存
                cache.Push(token);
                //TODO:处理信号量，信号量要加1
                clientSemaphore.Release();
            }
        }
    }
}
